import vtkCellPicker from "@kitware/vtk.js/Rendering/Core/CellPicker"
import vtkPointPicker from "@kitware/vtk.js/Rendering/Core/PointPicker"
import vtkOpenGLHardwareSelector from "@kitware/vtk.js/Rendering/OpenGL/HardwareSelector"
import { Vector3 } from "@kitware/vtk.js/types"
import {
  VTActor,
  BActor,
  BdfLoader,
  worldToDisplay,
  devtool,
  isPointInPolygon,
  cellInPolygonStrict,
  calculate2DPolygonCenter,
  unionBetweenPolygons,
  isPointInPolygon2,
  calculatePolygonCenter,
  PointerActor,
  hasArrayIntersection,
  radiansToDegrees,
} from "./index"
import { Vector3 as V3, Vector2, intersection_rectangle } from "stl-typescript"
import { Application } from "./App"
import { FieldAssociations } from "@kitware/vtk.js/Common/DataModel/DataSet/Constants"
// import vtkSelectionNode from "@kitware/vtk.js/Common/DataModel/SelectionNode";

export class SelectBox {
  parent: Selector
  selectBoxDom: HTMLElement
  container: HTMLElement
  enable: boolean = true
  isStart: boolean = false
  beginPoint: Vector2 = new Vector2(0, 0)
  endPoint: Vector2 = new Vector2(0, 0)
  width: number
  height: number
  x: number
  y: number

  onchange: (
    x1: number,
    y1: number,
    x2: number,
    y2: number,
    altKey: boolean
  ) => void | null = null
  mousedownHandler(ev: MouseEvent) {
    if (!this.enable) return
    if (ev.button === 2 && ev.shiftKey) {
      this.isStart = true
      this.beginPoint.set(ev.offsetX, ev.offsetY)
      this.selectBoxDom.style.display = "block"
      this.selectBoxDom.style.left = `${ev.offsetX}px`
      this.selectBoxDom.style.top = `${ev.offsetY}px`
      this.selectBoxDom.style.width = "0px"
      this.selectBoxDom.style.height = "0px"
    }
  }
  mousemoveHandler(ev: MouseEvent) {
    if (!this.enable || !this.isStart) return
    const width = ev.offsetX - this.beginPoint.x
    const height = ev.offsetY - this.beginPoint.y
    this.update(this.beginPoint.x, this.beginPoint.y, width, height)
  }
  mouseupHandler(ev: MouseEvent) {
    if (!this.enable || !this.isStart) return
    this.isStart = false
    this.selectBoxDom.style.display = "none"
    this.endPoint.set(ev.offsetX, ev.offsetY)
    this.onchange &&
      this.onchange(
        this.beginPoint.x,
        this.beginPoint.y,
        this.endPoint.x,
        this.endPoint.y,
        ev.altKey
      )
  }

  constructor(parent: Selector) {
    this.parent = parent
    this.destroy()
    this.createSelectBoxDom()
    this.register()
  }

  createSelectBoxDom() {
    if (this.selectBoxDom) return
    this.selectBoxDom = document.createElement("div")
    this.selectBoxDom.style.position = "absolute"
    this.selectBoxDom.style.border = "2px solid #74b9ff"
    this.selectBoxDom.style.backgroundColor = "rgba(116, 185, 255, 0.3)"
    this.selectBoxDom.style.pointerEvents = "none"
    this.selectBoxDom.style.display = "none"
    this.selectBoxDom.style.zIndex = "9999"
    this.container = this.parent.parent.container
    this.parent.parent.container.appendChild(this.selectBoxDom)
  }

  register() {
    document.addEventListener("mousedown", this.mousedownHandler.bind(this))
    document.addEventListener("mousemove", this.mousemoveHandler.bind(this))
    document.addEventListener("mouseup", this.mouseupHandler.bind(this))
  }

  destroy() {
    if (!this.container) return
    this.mousedownHandler &&
      document.removeEventListener("mousedown", this.mousedownHandler)
    this.mousemoveHandler &&
      document.removeEventListener("mousemove", this.mousemoveHandler)
    this.mouseupHandler &&
      document.removeEventListener("mouseup", this.mouseupHandler)
  }

  update(x: number, y: number, width: number, height: number): void {
    if (width >= 0 && height >= 0) {
      this.selectBoxDom.style.left = x + "px"
      this.selectBoxDom.style.top = y + "px"
      this.x = x
      this.y = y
    }

    if (width < 0 && height < 0) {
      this.selectBoxDom.style.left = x + width + "px"
      this.selectBoxDom.style.top = y + height + "px"
      this.x = x + width
      this.y = y + height
    }

    if (width > 0 && height < 0) {
      this.selectBoxDom.style.left = x + "px"
      this.selectBoxDom.style.top = y + height + "px"
      this.x = x
      this.y = y + height
    }

    if (width < 0 && height > 0) {
      this.selectBoxDom.style.left = x + width + "px"
      this.selectBoxDom.style.top = y + "px"
      this.x = x + width
      this.y = y
    }

    this.width = Math.abs(width)
    this.height = Math.abs(height)
    this.selectBoxDom.style.width = `${Math.abs(width)}px`
    this.selectBoxDom.style.height = `${Math.abs(height)}px`
  }

  getOutLinePoints() {
    return [
      [this.x, this.y],
      [this.x + this.width, this.y],
      [this.x + this.width, this.y + this.height],
      [this.x, this.y + this.height],
    ]
  }
}

export class Selector {
  MouseMoveHandler: any
  vtPicker: vtkCellPicker = vtkCellPicker.newInstance({})
  vtPointPicker = vtkPointPicker.newInstance()
  openGLPicker = vtkOpenGLHardwareSelector.newInstance({
    captureZValues: true,
  })
  parent: Application
  cellPickList = new Set<VTActor>()
  pointPickList = new Set<VTActor>()
  selectBox: SelectBox
  private isListening = false
  // @ 预计算的单元信息
  private cellsInfo = []

  constructor(parent: Application) {
    this.parent = parent
    this.vtPicker.setPickFromList(1)
    this.vtPicker.initializePickList()
    this.vtPointPicker.initializePickList()
    this.vtPointPicker.setPickFromList(1)
  }

  initOpenGLPicker() {
    this.openGLPicker.setFieldAssociation(
      FieldAssociations.FIELD_ASSOCIATION_CELLS
    )
    this.openGLPicker.attach(this.parent.instance, this.parent.renderer)
  }

  register() {
    if (this.isListening) return
    document.addEventListener("mousemove", this.MouseMoveHandler)
    this.isListening = true
  }

  destroy() {
    document.removeEventListener("mousemove", this.MouseMoveHandler)
  }

  /**
   * * 等同于 addToTargetList
   * @param vtActor
   * @deprecated
   */
  setTargetList(vtActor: BActor) {
    this.vtPicker.addPickList(vtActor.instance)
    this.cellPickList.add(vtActor as VTActor)
  }

  addToTargetList(vtActor: BActor) {
    this.vtPicker.addPickList(vtActor.instance)
    this.cellPickList.add(vtActor as VTActor)
  }

  removeFromTargetList(vtActor: BActor) {
    this.vtPicker.deletePickList(vtActor.instance)
    this.cellPickList.delete(vtActor as VTActor)
  }

  /**
   * * 等同于 removeFromTargetList
   * @param vtActor
   * @deprecated
   */
  removeTargetList(vtActor: BActor) {
    this.vtPicker.deletePickList(vtActor.instance)
    this.cellPickList.delete(vtActor as VTActor)
  }

  setPointTargetList(vtActor: BActor) {
    this.vtPointPicker.addPickList(vtActor.instance)
    this.pointPickList.add(vtActor as VTActor)
  }

  removePointTargetList(vtActor: BActor) {
    this.vtPointPicker.deletePickList(vtActor.instance)
    this.pointPickList.delete(vtActor as VTActor)
  }

  pickVertexPoint(point: Vector3, threshold = 2) {
    // 如果点选择列表为空，直接返回 null
    if (this.pointPickList.size === 0) return null;
  
    // 提取所有点的信息：将每个 actor 的顶点信息扁平化为一个数组
    const pointsArr = Array.from(this.pointPickList).flatMap((actor: any) =>
      actor.vertexes.map((vertex: any) => ({
        id: vertex.id,       // 点的唯一标识
        XYZ: vertex.XYZ,     // 点的坐标
        partId: actor.partId, // 点所属的partId
      }))
    );
  
    // 使用点选择器获取选中的点
    const pickedPoint = this.getPickedPoint(point);
    if (!pickedPoint) return null; // 如果没有选中点，返回 null
  
    // 找到距离最近的点：遍历所有点，计算与选中点的距离，找到小于阈值的点
    const closestPoint = pointsArr.find((pointData) => {
      const pointPosition = new V3(...pointData.XYZ); // 将点坐标转换为 V3 对象
      return pointPosition.distanceTo(pickedPoint) < threshold; // 计算距离
    });
  
    // 返回最近的点，如果没有找到则返回 null
    return closestPoint || null;
  }
  
  /**
   * 使用点选择器获取选中的点
   * @param point 选中的点坐标
   * @returns 返回选中的点坐标（V3 对象），如果没有选中点则返回 null
   */
  private getPickedPoint(point: Vector3): V3 | null {
    this.vtPointPicker.pick(point, this.parent.renderer); // 使用点选择器进行点选择
    const pickedPoints = this.vtPointPicker.getPickedPositions(); // 获取选中的点
    return pickedPoints.length > 0 ? new V3(...pickedPoints[0]) : null; // 返回第一个选中的点
  }
  
  // * 这一步将vtkCellPicker选中的vtkActor转为自定义的VTActor
  getPickedVTActors(): VTActor[] {
    const vtInstanceArr = this.vtPicker.getActors()
    const result: VTActor[] = []
    vtInstanceArr.forEach((item) => {
      this.cellPickList.forEach((cellPicActor) => {
        if (cellPicActor.instance === item) {
          result.push(cellPicActor)
        }
      })
    })
    return result
  }

  initBoxSelector() {
    this.selectBox = new SelectBox(this)
  }

  /**
   * @description 当有多个actor时可以从回调函数中获取更多的信息
   * @param x
   * @param y
   * @param callback (idx:number, targetIndex:number)=>void idx:选中的单元索引，targetIndex:选中的单元在cellPickList中的索引
   * @returns
   */
  pickCell(
    x: number,
    y: number,
    callback?: (idx: number, targetIndex: number) => void
  ) {
    this.vtPicker.pick([x, y, 0], this.parent.renderer)
    const idx = this.vtPicker.getCellId()
    // const a = Array.from(this.vtPicker.getPickedPositions()[0])
    // this.add3dTestMark(a[0],a[1],a[2])
    if (callback && idx !== -1) {
      const pickedAc = this.vtPicker.getActors()[0]
      this.cellPickList.entries()
      let count = 0
      this.cellPickList.forEach((ac) => {
        if (ac.instance === pickedAc) {
          callback(idx, count)
        }
        count++
      })
    }
    return idx
  }

  pickCellHardware(x: number, y: number) {
    return new Promise((resolve, reject) => {
      this.parent.hardWareSelector
        .getSourceDataAsync(this.parent.renderer, x, y, x, y)
        .then((result) => {
          const selections = result.generateSelection(x, y, x, y)
          if (selections.length === 0) {
            resolve(null)
          } else {
            const { attributeID } = selections[0].getProperties()
            resolve(attributeID)
          }
        })
    })
  }

  /**
   * @description 在屏幕上添加测试点
   * @param x
   * @param y - 非屏幕坐标系
   */
  private AddTestMask(x, y) {
    const s = document.createElement("div")
    s.style.position = "absolute"
    s.style.left = `${x}px`
    s.style.bottom = `${y}px`
    s.style.width = "5px"
    s.style.height = "5px"
    s.style.backgroundColor = "red"
    document.body.appendChild(s)
  }

  private add3dTestMark(x, y, z) {
    this.parent.add(
      new PointerActor([x, y, z], { radius: 50, color: [255, 0, 0, 255] })
    )
    this.parent.renderAtOnce()
  }

  /**
   * @description 预计算显示坐标
   */
  preComputeDisplayPos(loader: BdfLoader) {
    const cells = loader.getAllCellInfo()
    this.cellsInfo = cells
    const leftOffset = this.parent.container.offsetLeft
    const topOffset = this.parent.container.offsetTop
    const height = this.parent.container.clientHeight
    cells.forEach((cellInfo, index) => {
      const len = cellInfo.points.length
      cellInfo.display = []
      for (let i = 0; i < len / 3; i++) {
        const r = worldToDisplay(
          this.parent.renderer,
          cellInfo.points[i * 3],
          cellInfo.points[i * 3 + 1],
          cellInfo.points[i * 3 + 2]
        )
        cellInfo.display.push([
          r[0] + leftOffset,
          height - r[1] + topOffset,
          r[2], // * 深度值 📌
        ])
      }
    })
  }

  nearCamera(a: V3, b: V3) {
    const s = this.parent.camera.getPosition()
    const vt = new V3(...s)
    const d1 = a.distanceTo(vt.clone())
    const d2 = b.distanceTo(vt.clone())

    return d1 <= d2
  }

  quickOcclusionCulling(cellsInfo: any) {
    return new Promise((resolve, reject) => {
      cellsInfo.forEach(async (cellInfo, index) => {
        const attributeID: any = await this.pickCellHardware(
          cellInfo.displayCenter.x - this.parent.container.offsetLeft,
          this.parent.container.clientHeight -
            cellInfo.displayCenter.y +
            this.parent.container.offsetTop
        )
        cellInfo.hitID = attributeID
        if (index === cellsInfo.length - 1) {
          resolve(
            cellsInfo.filter(
              (cellInfo) => cellInfo.hitID === cellInfo.cellIndex
            )
          )
        }
      })
    })
  }

  /**
   * @description 传统射线法 性能较差
   * @param cellsInfo
   * @returns
   */
  cellOcclusionCulling(cellsInfo: any) {
    cellsInfo.forEach((cellInfo) => {
      const tmp = cellInfo.display.map((t) => new Vector2(t[0], t[1]))
      cellInfo.displayCenter = calculate2DPolygonCenter(tmp)
      const pickIndex = this.pickCell(
        cellInfo.displayCenter.x,
        cellInfo.displayCenter.y
      )
      cellInfo.pickIndex = pickIndex
    })
    return cellsInfo.filter(
      (cellInfo) => cellInfo.pickIndex === cellInfo.cellIndex
    )
  }

  /**
   * @description 找出所有在矩形范围内的单元 - 按照cell几何中心筛选
   */
  pickCellsInRectangleBox(back = false) {
    return new Promise((resolve) => {
      if (this.cellsInfo.length === 0) {
        devtool.error("未预计算显示坐标，请先调用preComputeDisplayPos方法")
        return
      }
      let result = []
      const output = this.selectBox.getOutLinePoints()
      const time1 = performance.now()
      const offsetLeft = this.parent.container.offsetLeft
      const offsetTop = this.parent.container.offsetTop
      this.cellsInfo.forEach((cellInfo, index) => {
        const tmp = cellInfo.display.map((t) => new Vector2(t[0], t[1]))
        cellInfo.displayCenter = calculate2DPolygonCenter(tmp)
        if (
          isPointInPolygon(
            new Vector2(
              cellInfo.displayCenter.x - offsetLeft,
              cellInfo.displayCenter.y - offsetTop
            ),
            [
              new Vector2(output[0][0], output[0][1]),
              new Vector2(output[1][0], output[1][1]),
              new Vector2(output[2][0], output[2][1]),
              new Vector2(output[3][0], output[3][1]),
            ]
          )
        ) {
          result.push(cellInfo)
        }
      })
      const time2 = performance.now()
      devtool.success("pickCellsInRectangleBox", time2 - time1, "ms")
      if (!back) {
        this.quickOcclusionCulling(result).then((r) => {
          resolve(r)
        })
        devtool.success(
          "quickOcclusionCulling",
          performance.now() - time2,
          "ms"
        )
      } else {
        resolve(result)
      }
    })
  }

  pickPoint(x: number, y: number) {
    this.vtPointPicker.pick([x, y, 0], this.parent.renderer)
    return this.vtPointPicker.getPickedPositions()[0]
  }

  pickEdge(xp: number, yp: number, tolerance: number = 0.05) {
    const x1 = Math.floor(xp - tolerance)
    const y1 = Math.floor(yp - tolerance)
    const x2 = Math.ceil(xp + tolerance)
    const y2 = Math.ceil(yp + tolerance)
    this.openGLPicker.setArea(x1, y1, x2, y2)
    const selections = this.openGLPicker.select()
    return selections[0].getSelectionList()
  }

  // TODO 暂时不要用
  private highLevelPickPoint(xp: number, yp: number, tolerance: number = 0.05) {
    const x1 = Math.floor(xp - tolerance)
    const y1 = Math.floor(yp - tolerance)
    const x2 = Math.ceil(xp + tolerance)
    const y2 = Math.ceil(yp + tolerance)
    this.openGLPicker.setArea(x1, y1, x2, y2)
    const selector = this.openGLPicker
    const openGLRenderWindow = this.parent.instance
    const renderer = this.parent.renderer
    if (selector.captureBuffers()) {
      const pos = [xp, yp] as any
      const outSelectedPosition = [0, 0] as any
      const info = selector.getPixelInformation(
        pos,
        tolerance,
        outSelectedPosition
      )

      console.log(info)

      if (info == null || info.prop == null) return []

      const startPoint = openGLRenderWindow.displayToWorld(
        Math.round((x1 + x2) / 2),
        Math.round((y1 + y2) / 2),
        0,
        renderer
      )

      const endPoint = openGLRenderWindow.displayToWorld(
        Math.round((x1 + x2) / 2),
        Math.round((y1 + y2) / 2),
        1,
        renderer
      )

      const ray = [Array.from(startPoint), Array.from(endPoint)]

      const worldPosition = Array.from(
        openGLRenderWindow.displayToWorld(
          info.displayPosition[0],
          info.displayPosition[1],
          info.zValue,
          renderer
        )
      )

      const displayPosition = [
        info.displayPosition[0],
        info.displayPosition[1],
        info.zValue,
      ]

      const selection = []
      selection[0] = {
        worldPosition,
        displayPosition,
        compositeID: info.compositeID,
        ...info.prop.get("representationId"),
        ray,
      }
      return selection
    }
    return []
  }

  /**
   *
   * @param loader
   * @param startIndex
   * @param degrees 角度
   * @returns 返回符合条件的单元索引
   */
  pickCellByFaceForBdf(
    ac: VTActor,
    loader: BdfLoader,
    startIndex: number,
    degrees: number = 5
  ) {
    const cellsIdIndexMap = loader.cellsIdIndexMap
    let res = []
    ac.createNormalActor(true)
    const nodeStart = [
      ac.normals[3 * startIndex],
      ac.normals[3 * startIndex + 1],
      ac.normals[3 * startIndex + 2],
    ]
    const visited = new Set()
    function recursion(start, dataMap) {
      if(visited.has(start)) return
      visited.add(start)
      for (let key in dataMap) {
        if (visited.has(+key)) continue
        // * 找出相邻单元
        if (hasArrayIntersection(dataMap[key], dataMap[start])) {
          // * 判断法向量夹角
          if (+key !== startIndex) {
            const i = +key
            const node1 = [
              ac.normals[3 * i],
              ac.normals[3 * i + 1],
              ac.normals[3 * i + 2],
            ]
            const angle = new V3(...nodeStart).angleTo(new V3(...node1))
            if (radiansToDegrees(angle) < degrees) {
              res.push(i)
              recursion(i, dataMap)
            }
          }
        }
      }
    }
    recursion(startIndex, cellsIdIndexMap)
    return res
  }
}
